qutebrowser 源码阅读

介绍

qutebrowser 是一个基于 Qt 的浏览器,基于 PyQt/PySide 框架,使用 Python 语言开发。

该项目最大的特色,它是一个由键盘驱动的 Vim 风格的浏览器。是不是有点懵?浏览器咋还和 Vim 挂上勾了。是这样,在 qutebrowser 中,完全通过键盘即可完成所有浏览器操作,所以说是键盘驱动。键盘怎么驱动呢?qutebrowser 类似 Vim,也设计了众多模式、快捷键、命令,通过键盘触发各种命令,可完成通常由鼠标完成的操作。

系列

价值

是不是感觉有点蛋疼?先别急着疼,这个项目,它浑身都是宝。怎么说?如下:

首先,浏览器在信息时代的重要性不用多说了吧。对于主流浏览器,如果你想个性化定制怎么办?答曰插件、油猴等,但他们均有限制。更加自在的方式是从头开发属于自己的浏览器,任何方面都可定制,并灵活与其它系统打通。假设你是技术人员,可以列数以下具备浏览器能力的框架,你会发现,Qt 的浏览器底层封装,老当益壮。

其次,作为一个移动端工程师,写了十年UI,如今我已经厌倦了写 UI。我希望所有人机交互都像 1.1 Emacs/Vim 那样,通过模式与命令解决。这是一种古老的、被严重低估的、强大的人机交互模式。通过 qutebrowser 这个项目,将学会如何使用 Python 来实现这种模式。

第三,qutebrowser 本身是一个中大型的 Python 工程。由于 Python 语言的灵活性,很容易将代码越写越乱。qutebrowser 作为优秀案例,展示了如何基于灵活语法,维护一个高度有序工程化的 Python 项目。

感觉有点意思?跟随我在本文中展开探索吧!

写作方式

源码阅读类的文章,很容易变成代码的堆砌:作者机械地大段复制代码,章节出现割裂,混乱不堪。读者读起来也十分痛苦,需要忍受大段无用代码,从中寻找那么一两句有价值地洞见。

在本文中,我将竭力避免这一点。我希望自己是将代码全部理解后,按照人认知的维度重新排列。同时省去不重要的细节,突出那些更重要、有价值的部分。

如果你也开始学习 qutebrowser 的源码,希望查询某个文件、某个类的详细讲解,则可以参见《qutebrowser 符号表》,在这篇笔记中,我按照符号和文件的维度对该项目进行介绍,将更加方便检索。许多位于本文之外的知识,也可借由这篇笔记,进行补充了解。

本文也涉及了大量 Qt 相关知识,尤其是 Qt QWebEngine,如果感兴趣的话,直接点击这两个链接,也可访问到对应的知识内容。

采用一种独特的代码阅读方式《通过临摹阅读开源代码》,具体可点击链接进行了解。这是一种底层、扎实的学习方法。

基于原有仓库魔改

我想基于 qutebrowser 官方代码进行修改,比如添加注释,或者定制功能。同时,我想将我修改的仓库,提交到我自己的 Git server 中。我该怎么做呢?

首先 clone qutebrowser 的源码,将 main 分支 clone 到本地。

此时,origin remote 指向官方仓库。

接下来,添加一个我的 git server 的 remote:

git remote add nas ssh://git@.../maxiee/qutebrowser.git

创建一条分支,用于自己魔改:

git checkout -b maxiee

接下来几个操作:

同步官方源码:

main 分支不做任何修改,需要同步官方代码时:git pull

将官方代码合入魔改分支:

git checkout maxiee
git merge main

将魔改分支提交到我自己的 Git server 中:

git push nas maxiee

将魔 main 分支提交到我自己的 Git server 中:

git push nas main

源码入口

有两个入口:

功能相同,均调用 qutebrowser\qutebrowser.py 中的 main 方法。在 main 方法中:

  1. 解析传入命令行的参数
  2. 调用 qutebrowser\app.pyrun 方法,进行初始化,并创建 Application(定义在该文件中)。
  3. 在初始化过程中,也同时创建了主窗口 MainWindow

初始化流程

布局框架

MainWindow 中定义了整个程序的布局,如下图所示:

该布局由多层堆叠构成,在图中我将堆叠层级横向展开了。其中:

对应的核心类属性:

  • _overlays:层级列表
  • _vbox:最底层核心界面的纵向布局
  • tabbed_browser:Tab 分页的浏览器(TabbedBrowser

命令

作为由键盘驱动的浏览器,qutebrowser 最大的特色是包含了众多命令,各种快捷键映射到这些命令上,来实现所谓键盘驱动。

在工程中全局搜索 @cmdutils.register( 即可列出所有命令。该装饰器的作用是注册命令,具体参见 qutebrowser register

qutebrowser 命令列表》列表中记录了所有命令,可通过该文了解它们的具体实现。

仿照已有命令,编写自己的命令十分有趣。以下是我的尝试:

事件

拦截

QApplication 添加 Qt 事件过滤器,由 qutebrowser EventFilter 负责接收处理事件。

处理

eventFilter 方法

EventFilter 监听了 QApplication,程序的事件将由 eventFilter 方法接收 QEvent

EventFilter 只拦截3类事件:KeyPress、KeyRelease、ShortcutOverride。定义在 _handlers 属性中。同时,只响应 QWindow 类实例发出的事件。

_handlers 是一个字典,分别包含了不同类型的响应函数。

键盘事件

_handle_key_event

KeyPress 和 KeyRelease 事件都会由该方法进行响应。

首先获取当前活跃的窗口、活跃的 ModeManager,将事件交给 ModeManager 进行处理。

模态交互

qutebrowser 实现了一种类似 1.1 Emacs/Vim 的由键盘驱动的模态交互。该部分实现位于 ModeManager

KeyMode

首先,键盘按键是有模态的,所谓模态,是指程序处于某种状态。同一个按键,在不同状态下按下,功能是不同的。KeyMode 定义了所有的按键模式。

每种模式都有一个 Parser 与之对应,用于处理不同模式下的按键。对应关系参见 KeyMode

Config 篇

qutebrowser 具有丰富的配置项,可定制性还是相当不错的。在程序中,相关代码都在 config 包下。

config 部分的代码,尽管它在程序功能中属于边缘功能,但是由于丰富的配置项,导致它在程序中无处不再,因此有必要先搞明白 Config 模块。

话题清单


本文作者:Maeiee

本文链接:qutebrowser 源码阅读

版权声明:如无特别声明,本文即为原创文章,版权归 Maeiee 所有,未经允许不得转载!


喜欢我文章的朋友请随缘打赏,鼓励我创作更多更好的作品!